//==============================================================================
// Copyright (c) 2008-2013 Niall Ryan. All Rights Reserved.
//==============================================================================

#include <gtest/gtest.h>

#include "hydra/stacks/EliminationStack.h"
#include "hydra/stacks/LockFreeStack.h"
#include "hydra/stacks/LockFreeStampedStack.h"

using namespace Hydra;

template<typename Stack>
class StackTest : public ::testing::Test
{
protected:
	static const int NUM_THREAD_PAIRS = 2;
	static const int NUM_ITERATIONS = 50000;
	static void Push(StackTest* test)
	{
		Stack& stack = test->m_stack;
		for( int i=1; i<=NUM_ITERATIONS; ++i )
		{
			stack.push(i);
			test->m_total.getAndAdd(i);
		}
	}
	static void Pop(StackTest* test)
	{
		Stack& stack = test->m_stack;
		int numPopped = 0;
		while( true )
		{
			int value;
			if( stack.pop(&value) )
			{
				test->m_total.getAndAdd(-value);
				++numPopped;
				if( numPopped == NUM_ITERATIONS )
					break;
			}
		}
	}

	Stack m_stack;
	Atomic<int> m_total;
};

typedef ::testing::Types<
    LockFreeStack<int>,
	LockFreeStampedStack<int>,
	EliminationStack<int>
    > StackTestTypes;
TYPED_TEST_CASE(StackTest, StackTestTypes);

TYPED_TEST(StackTest, Stuff)
{
	//TEST_TIMER;

	//
	//single threaded functionality tests
	//
	int value;
	EXPECT_TRUE(!m_stack.pop(&value));
	m_stack.push(10);
	EXPECT_TRUE(m_stack.pop(&value));
	EXPECT_TRUE(value==10);
	EXPECT_TRUE(!m_stack.pop(&value));
	m_stack.push(50);
	m_stack.push(60);
	m_stack.push(70);
	m_stack.push(80);
	EXPECT_TRUE(m_stack.pop(&value));
	EXPECT_TRUE(value==80);
	EXPECT_TRUE(m_stack.pop(&value));
	EXPECT_TRUE(value==70);
	EXPECT_TRUE(m_stack.pop(&value));
	EXPECT_TRUE(value==60);
	EXPECT_TRUE(m_stack.pop(&value));
	EXPECT_TRUE(value==50);
	EXPECT_TRUE(!m_stack.pop(&value));

	//
	//multithread tests
	//
	Thread threads[NUM_THREAD_PAIRS*2];
	{
		m_total = 0;
		for( int i=0; i<NUM_THREAD_PAIRS; ++i )
		{
			threads[i*2+0] = Thread(&Push, this);
			threads[i*2+1] = Thread(&Pop, this);
		}
		for( int i=0; i<NUM_THREAD_PAIRS*2; ++i )
			threads[i].Start();
		for( int i=0; i<NUM_THREAD_PAIRS*2; ++i )
			threads[i].Join();
		EXPECT_TRUE(m_total==0);
		EXPECT_TRUE(!m_stack.pop(&value));
	}
}
